##// END OF EJS Templates
hghave: add a check for the `xz` compression utility...
Matt Harbison -
r44098:21e05aab default
parent child Browse files
Show More
@@ -1,1010 +1,1017 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 _bytespath(p):
33 33 if p is None:
34 34 return p
35 35 return p.encode('utf-8')
36 36
37 37 def _strpath(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 _bytespath(p):
46 46 return p
47 47
48 48 _strpath = _bytespath
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 @checkvers(
318 318 "hg", "Mercurial >= %s", list([(1.0 * x) / 10 for x in range(9, 99)])
319 319 )
320 320 def has_hg_range(v):
321 321 major, minor = v.split('.')[0:2]
322 322 return gethgversion() >= (int(major), int(minor))
323 323
324 324
325 325 @check("hg08", "Mercurial >= 0.8")
326 326 def has_hg08():
327 327 if checks["hg09"][0]():
328 328 return True
329 329 return matchoutput('hg help annotate 2>&1', '--date')
330 330
331 331
332 332 @check("hg07", "Mercurial >= 0.7")
333 333 def has_hg07():
334 334 if checks["hg08"][0]():
335 335 return True
336 336 return matchoutput('hg --version --quiet 2>&1', 'Mercurial Distributed SCM')
337 337
338 338
339 339 @check("hg06", "Mercurial >= 0.6")
340 340 def has_hg06():
341 341 if checks["hg07"][0]():
342 342 return True
343 343 return matchoutput('hg --version --quiet 2>&1', 'Mercurial version')
344 344
345 345
346 346 @check("gettext", "GNU Gettext (msgfmt)")
347 347 def has_gettext():
348 348 return matchoutput('msgfmt --version', br'GNU gettext-tools')
349 349
350 350
351 351 @check("git", "git command line client")
352 352 def has_git():
353 353 return matchoutput('git --version 2>&1', br'^git version')
354 354
355 355
356 356 def getgitversion():
357 357 m = matchoutput('git --version 2>&1', br'git version (\d+)\.(\d+)')
358 358 if not m:
359 359 return (0, 0)
360 360 return (int(m.group(1)), int(m.group(2)))
361 361
362 362
363 363 # https://github.com/git-lfs/lfs-test-server
364 364 @check("lfs-test-server", "git-lfs test server")
365 365 def has_lfsserver():
366 366 exe = 'lfs-test-server'
367 367 if has_windows():
368 368 exe = 'lfs-test-server.exe'
369 369 return any(
370 370 os.access(os.path.join(path, exe), os.X_OK)
371 371 for path in os.environ["PATH"].split(os.pathsep)
372 372 )
373 373
374 374
375 375 @checkvers("git", "git client (with ext::sh support) version >= %s", (1.9,))
376 376 def has_git_range(v):
377 377 major, minor = v.split('.')[0:2]
378 378 return getgitversion() >= (int(major), int(minor))
379 379
380 380
381 381 @check("docutils", "Docutils text processing library")
382 382 def has_docutils():
383 383 try:
384 384 import docutils.core
385 385
386 386 docutils.core.publish_cmdline # silence unused import
387 387 return True
388 388 except ImportError:
389 389 return False
390 390
391 391
392 392 def getsvnversion():
393 393 m = matchoutput('svn --version --quiet 2>&1', br'^(\d+)\.(\d+)')
394 394 if not m:
395 395 return (0, 0)
396 396 return (int(m.group(1)), int(m.group(2)))
397 397
398 398
399 399 @checkvers("svn", "subversion client and admin tools >= %s", (1.3, 1.5))
400 400 def has_svn_range(v):
401 401 major, minor = v.split('.')[0:2]
402 402 return getsvnversion() >= (int(major), int(minor))
403 403
404 404
405 405 @check("svn", "subversion client and admin tools")
406 406 def has_svn():
407 407 return matchoutput('svn --version 2>&1', br'^svn, version') and matchoutput(
408 408 'svnadmin --version 2>&1', br'^svnadmin, version'
409 409 )
410 410
411 411
412 412 @check("svn-bindings", "subversion python bindings")
413 413 def has_svn_bindings():
414 414 try:
415 415 import svn.core
416 416
417 417 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
418 418 if version < (1, 4):
419 419 return False
420 420 return True
421 421 except ImportError:
422 422 return False
423 423
424 424
425 425 @check("p4", "Perforce server and client")
426 426 def has_p4():
427 427 return matchoutput('p4 -V', br'Rev\. P4/') and matchoutput(
428 428 'p4d -V', br'Rev\. P4D/'
429 429 )
430 430
431 431
432 432 @check("symlink", "symbolic links")
433 433 def has_symlink():
434 434 # mercurial.windows.checklink() is a hard 'no' at the moment
435 435 if os.name == 'nt' or getattr(os, "symlink", None) is None:
436 436 return False
437 437 name = tempfile.mktemp(dir='.', prefix=tempprefix)
438 438 try:
439 439 os.symlink(".", name)
440 440 os.unlink(name)
441 441 return True
442 442 except (OSError, AttributeError):
443 443 return False
444 444
445 445
446 446 @check("hardlink", "hardlinks")
447 447 def has_hardlink():
448 448 from mercurial import util
449 449
450 450 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
451 451 os.close(fh)
452 452 name = tempfile.mktemp(dir='.', prefix=tempprefix)
453 453 try:
454 454 util.oslink(_bytespath(fn), _bytespath(name))
455 455 os.unlink(name)
456 456 return True
457 457 except OSError:
458 458 return False
459 459 finally:
460 460 os.unlink(fn)
461 461
462 462
463 463 @check("hardlink-whitelisted", "hardlinks on whitelisted filesystems")
464 464 def has_hardlink_whitelisted():
465 465 from mercurial import util
466 466
467 467 try:
468 468 fstype = util.getfstype(b'.')
469 469 except OSError:
470 470 return False
471 471 return fstype in util._hardlinkfswhitelist
472 472
473 473
474 474 @check("rmcwd", "can remove current working directory")
475 475 def has_rmcwd():
476 476 ocwd = os.getcwd()
477 477 temp = tempfile.mkdtemp(dir='.', prefix=tempprefix)
478 478 try:
479 479 os.chdir(temp)
480 480 # On Linux, 'rmdir .' isn't allowed, but the other names are okay.
481 481 # On Solaris and Windows, the cwd can't be removed by any names.
482 482 os.rmdir(os.getcwd())
483 483 return True
484 484 except OSError:
485 485 return False
486 486 finally:
487 487 os.chdir(ocwd)
488 488 # clean up temp dir on platforms where cwd can't be removed
489 489 try:
490 490 os.rmdir(temp)
491 491 except OSError:
492 492 pass
493 493
494 494
495 495 @check("tla", "GNU Arch tla client")
496 496 def has_tla():
497 497 return matchoutput('tla --version 2>&1', br'The GNU Arch Revision')
498 498
499 499
500 500 @check("gpg", "gpg client")
501 501 def has_gpg():
502 502 return matchoutput('gpg --version 2>&1', br'GnuPG')
503 503
504 504
505 505 @check("gpg2", "gpg client v2")
506 506 def has_gpg2():
507 507 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.')
508 508
509 509
510 510 @check("gpg21", "gpg client v2.1+")
511 511 def has_gpg21():
512 512 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.(?!0)')
513 513
514 514
515 515 @check("unix-permissions", "unix-style permissions")
516 516 def has_unix_permissions():
517 517 d = tempfile.mkdtemp(dir='.', prefix=tempprefix)
518 518 try:
519 519 fname = os.path.join(d, 'foo')
520 520 for umask in (0o77, 0o07, 0o22):
521 521 os.umask(umask)
522 522 f = open(fname, 'w')
523 523 f.close()
524 524 mode = os.stat(fname).st_mode
525 525 os.unlink(fname)
526 526 if mode & 0o777 != ~umask & 0o666:
527 527 return False
528 528 return True
529 529 finally:
530 530 os.rmdir(d)
531 531
532 532
533 533 @check("unix-socket", "AF_UNIX socket family")
534 534 def has_unix_socket():
535 535 return getattr(socket, 'AF_UNIX', None) is not None
536 536
537 537
538 538 @check("root", "root permissions")
539 539 def has_root():
540 540 return getattr(os, 'geteuid', None) and os.geteuid() == 0
541 541
542 542
543 543 @check("pyflakes", "Pyflakes python linter")
544 544 def has_pyflakes():
545 545 return matchoutput(
546 546 "sh -c \"echo 'import re' 2>&1 | pyflakes\"",
547 547 br"<stdin>:1: 're' imported but unused",
548 548 True,
549 549 )
550 550
551 551
552 552 @check("pylint", "Pylint python linter")
553 553 def has_pylint():
554 554 return matchoutput("pylint --help", br"Usage: pylint", True)
555 555
556 556
557 557 @check("clang-format", "clang-format C code formatter")
558 558 def has_clang_format():
559 559 m = matchoutput('clang-format --version', br'clang-format version (\d)')
560 560 # style changed somewhere between 4.x and 6.x
561 561 return m and int(m.group(1)) >= 6
562 562
563 563
564 564 @check("jshint", "JSHint static code analysis tool")
565 565 def has_jshint():
566 566 return matchoutput("jshint --version 2>&1", br"jshint v")
567 567
568 568
569 569 @check("pygments", "Pygments source highlighting library")
570 570 def has_pygments():
571 571 try:
572 572 import pygments
573 573
574 574 pygments.highlight # silence unused import warning
575 575 return True
576 576 except ImportError:
577 577 return False
578 578
579 579
580 580 @check("outer-repo", "outer repo")
581 581 def has_outer_repo():
582 582 # failing for other reasons than 'no repo' imply that there is a repo
583 583 return not matchoutput('hg root 2>&1', br'abort: no repository found', True)
584 584
585 585
586 586 @check("ssl", "ssl module available")
587 587 def has_ssl():
588 588 try:
589 589 import ssl
590 590
591 591 ssl.CERT_NONE
592 592 return True
593 593 except ImportError:
594 594 return False
595 595
596 596
597 597 @check("sslcontext", "python >= 2.7.9 ssl")
598 598 def has_sslcontext():
599 599 try:
600 600 import ssl
601 601
602 602 ssl.SSLContext
603 603 return True
604 604 except (ImportError, AttributeError):
605 605 return False
606 606
607 607
608 608 @check("defaultcacerts", "can verify SSL certs by system's CA certs store")
609 609 def has_defaultcacerts():
610 610 from mercurial import sslutil, ui as uimod
611 611
612 612 ui = uimod.ui.load()
613 613 return sslutil._defaultcacerts(ui) or sslutil._canloaddefaultcerts
614 614
615 615
616 616 @check("defaultcacertsloaded", "detected presence of loaded system CA certs")
617 617 def has_defaultcacertsloaded():
618 618 import ssl
619 619 from mercurial import sslutil, ui as uimod
620 620
621 621 if not has_defaultcacerts():
622 622 return False
623 623 if not has_sslcontext():
624 624 return False
625 625
626 626 ui = uimod.ui.load()
627 627 cafile = sslutil._defaultcacerts(ui)
628 628 ctx = ssl.create_default_context()
629 629 if cafile:
630 630 ctx.load_verify_locations(cafile=cafile)
631 631 else:
632 632 ctx.load_default_certs()
633 633
634 634 return len(ctx.get_ca_certs()) > 0
635 635
636 636
637 637 @check("tls1.2", "TLS 1.2 protocol support")
638 638 def has_tls1_2():
639 639 from mercurial import sslutil
640 640
641 641 return b'tls1.2' in sslutil.supportedprotocols
642 642
643 643
644 644 @check("windows", "Windows")
645 645 def has_windows():
646 646 return os.name == 'nt'
647 647
648 648
649 649 @check("system-sh", "system() uses sh")
650 650 def has_system_sh():
651 651 return os.name != 'nt'
652 652
653 653
654 654 @check("serve", "platform and python can manage 'hg serve -d'")
655 655 def has_serve():
656 656 return True
657 657
658 658
659 659 @check("test-repo", "running tests from repository")
660 660 def has_test_repo():
661 661 t = os.environ["TESTDIR"]
662 662 return os.path.isdir(os.path.join(t, "..", ".hg"))
663 663
664 664
665 665 @check("tic", "terminfo compiler and curses module")
666 666 def has_tic():
667 667 try:
668 668 import curses
669 669
670 670 curses.COLOR_BLUE
671 671 return matchoutput('test -x "`which tic`"', br'')
672 672 except ImportError:
673 673 return False
674 674
675 675
676 @check("xz", "xz compression utility")
677 def has_xz():
678 # When Windows invokes a subprocess in shell mode, it uses `cmd.exe`, which
679 # only knows `where`, not `which`. So invoke MSYS shell explicitly.
680 return matchoutput("sh -c 'test -x \"`which xz`\"'", b'')
681
682
676 683 @check("msys", "Windows with MSYS")
677 684 def has_msys():
678 685 return os.getenv('MSYSTEM')
679 686
680 687
681 688 @check("aix", "AIX")
682 689 def has_aix():
683 690 return sys.platform.startswith("aix")
684 691
685 692
686 693 @check("osx", "OS X")
687 694 def has_osx():
688 695 return sys.platform == 'darwin'
689 696
690 697
691 698 @check("osxpackaging", "OS X packaging tools")
692 699 def has_osxpackaging():
693 700 try:
694 701 return (
695 702 matchoutput('pkgbuild', br'Usage: pkgbuild ', ignorestatus=1)
696 703 and matchoutput(
697 704 'productbuild', br'Usage: productbuild ', ignorestatus=1
698 705 )
699 706 and matchoutput('lsbom', br'Usage: lsbom', ignorestatus=1)
700 707 and matchoutput('xar --help', br'Usage: xar', ignorestatus=1)
701 708 )
702 709 except ImportError:
703 710 return False
704 711
705 712
706 713 @check('linuxormacos', 'Linux or MacOS')
707 714 def has_linuxormacos():
708 715 # This isn't a perfect test for MacOS. But it is sufficient for our needs.
709 716 return sys.platform.startswith(('linux', 'darwin'))
710 717
711 718
712 719 @check("docker", "docker support")
713 720 def has_docker():
714 721 pat = br'A self-sufficient runtime for'
715 722 if matchoutput('docker --help', pat):
716 723 if 'linux' not in sys.platform:
717 724 # TODO: in theory we should be able to test docker-based
718 725 # package creation on non-linux using boot2docker, but in
719 726 # practice that requires extra coordination to make sure
720 727 # $TESTTEMP is going to be visible at the same path to the
721 728 # boot2docker VM. If we figure out how to verify that, we
722 729 # can use the following instead of just saying False:
723 730 # return 'DOCKER_HOST' in os.environ
724 731 return False
725 732
726 733 return True
727 734 return False
728 735
729 736
730 737 @check("debhelper", "debian packaging tools")
731 738 def has_debhelper():
732 739 # Some versions of dpkg say `dpkg', some say 'dpkg' (` vs ' on the first
733 740 # quote), so just accept anything in that spot.
734 741 dpkg = matchoutput(
735 742 'dpkg --version', br"Debian .dpkg' package management program"
736 743 )
737 744 dh = matchoutput(
738 745 'dh --help', br'dh is a part of debhelper.', ignorestatus=True
739 746 )
740 747 dh_py2 = matchoutput(
741 748 'dh_python2 --help', br'other supported Python versions'
742 749 )
743 750 # debuild comes from the 'devscripts' package, though you might want
744 751 # the 'build-debs' package instead, which has a dependency on devscripts.
745 752 debuild = matchoutput(
746 753 'debuild --help', br'to run debian/rules with given parameter'
747 754 )
748 755 return dpkg and dh and dh_py2 and debuild
749 756
750 757
751 758 @check(
752 759 "debdeps", "debian build dependencies (run dpkg-checkbuilddeps in contrib/)"
753 760 )
754 761 def has_debdeps():
755 762 # just check exit status (ignoring output)
756 763 path = '%s/../contrib/packaging/debian/control' % os.environ['TESTDIR']
757 764 return matchoutput('dpkg-checkbuilddeps %s' % path, br'')
758 765
759 766
760 767 @check("demandimport", "demandimport enabled")
761 768 def has_demandimport():
762 769 # chg disables demandimport intentionally for performance wins.
763 770 return (not has_chg()) and os.environ.get('HGDEMANDIMPORT') != 'disable'
764 771
765 772
766 773 # Add "py27", "py35", ... as possible feature checks. Note that there's no
767 774 # punctuation here.
768 775 @checkvers("py", "Python >= %s", (2.7, 3.5, 3.6, 3.7, 3.8, 3.9))
769 776 def has_python_range(v):
770 777 major, minor = v.split('.')[0:2]
771 778 py_major, py_minor = sys.version_info.major, sys.version_info.minor
772 779
773 780 return (py_major, py_minor) >= (int(major), int(minor))
774 781
775 782
776 783 @check("py3", "running with Python 3.x")
777 784 def has_py3():
778 785 return 3 == sys.version_info[0]
779 786
780 787
781 788 @check("py3exe", "a Python 3.x interpreter is available")
782 789 def has_python3exe():
783 790 return matchoutput('python3 -V', br'^Python 3.(5|6|7|8|9)')
784 791
785 792
786 793 @check("pure", "running with pure Python code")
787 794 def has_pure():
788 795 return any(
789 796 [
790 797 os.environ.get("HGMODULEPOLICY") == "py",
791 798 os.environ.get("HGTEST_RUN_TESTS_PURE") == "--pure",
792 799 ]
793 800 )
794 801
795 802
796 803 @check("slow", "allow slow tests (use --allow-slow-tests)")
797 804 def has_slow():
798 805 return os.environ.get('HGTEST_SLOW') == 'slow'
799 806
800 807
801 808 @check("hypothesis", "Hypothesis automated test generation")
802 809 def has_hypothesis():
803 810 try:
804 811 import hypothesis
805 812
806 813 hypothesis.given
807 814 return True
808 815 except ImportError:
809 816 return False
810 817
811 818
812 819 @check("unziplinks", "unzip(1) understands and extracts symlinks")
813 820 def unzip_understands_symlinks():
814 821 return matchoutput('unzip --help', br'Info-ZIP')
815 822
816 823
817 824 @check("zstd", "zstd Python module available")
818 825 def has_zstd():
819 826 try:
820 827 import mercurial.zstd
821 828
822 829 mercurial.zstd.__version__
823 830 return True
824 831 except ImportError:
825 832 return False
826 833
827 834
828 835 @check("devfull", "/dev/full special file")
829 836 def has_dev_full():
830 837 return os.path.exists('/dev/full')
831 838
832 839
833 840 @check("ensurepip", "ensurepip module")
834 841 def has_ensurepip():
835 842 try:
836 843 import ensurepip
837 844
838 845 ensurepip.bootstrap
839 846 return True
840 847 except ImportError:
841 848 return False
842 849
843 850
844 851 @check("virtualenv", "Python virtualenv support")
845 852 def has_virtualenv():
846 853 try:
847 854 import virtualenv
848 855
849 856 virtualenv.ACTIVATE_SH
850 857 return True
851 858 except ImportError:
852 859 return False
853 860
854 861
855 862 @check("fsmonitor", "running tests with fsmonitor")
856 863 def has_fsmonitor():
857 864 return 'HGFSMONITOR_TESTS' in os.environ
858 865
859 866
860 867 @check("fuzzywuzzy", "Fuzzy string matching library")
861 868 def has_fuzzywuzzy():
862 869 try:
863 870 import fuzzywuzzy
864 871
865 872 fuzzywuzzy.__version__
866 873 return True
867 874 except ImportError:
868 875 return False
869 876
870 877
871 878 @check("clang-libfuzzer", "clang new enough to include libfuzzer")
872 879 def has_clang_libfuzzer():
873 880 mat = matchoutput('clang --version', br'clang version (\d)')
874 881 if mat:
875 882 # libfuzzer is new in clang 6
876 883 return int(mat.group(1)) > 5
877 884 return False
878 885
879 886
880 887 @check("clang-6.0", "clang 6.0 with version suffix (libfuzzer included)")
881 888 def has_clang60():
882 889 return matchoutput('clang-6.0 --version', br'clang version 6\.')
883 890
884 891
885 892 @check("xdiff", "xdiff algorithm")
886 893 def has_xdiff():
887 894 try:
888 895 from mercurial import policy
889 896
890 897 bdiff = policy.importmod('bdiff')
891 898 return bdiff.xdiffblocks(b'', b'') == [(0, 0, 0, 0)]
892 899 except (ImportError, AttributeError):
893 900 return False
894 901
895 902
896 903 @check('extraextensions', 'whether tests are running with extra extensions')
897 904 def has_extraextensions():
898 905 return 'HGTESTEXTRAEXTENSIONS' in os.environ
899 906
900 907
901 908 def getrepofeatures():
902 909 """Obtain set of repository features in use.
903 910
904 911 HGREPOFEATURES can be used to define or remove features. It contains
905 912 a space-delimited list of feature strings. Strings beginning with ``-``
906 913 mean to remove.
907 914 """
908 915 # Default list provided by core.
909 916 features = {
910 917 'bundlerepo',
911 918 'revlogstore',
912 919 'fncache',
913 920 }
914 921
915 922 # Features that imply other features.
916 923 implies = {
917 924 'simplestore': ['-revlogstore', '-bundlerepo', '-fncache'],
918 925 }
919 926
920 927 for override in os.environ.get('HGREPOFEATURES', '').split(' '):
921 928 if not override:
922 929 continue
923 930
924 931 if override.startswith('-'):
925 932 if override[1:] in features:
926 933 features.remove(override[1:])
927 934 else:
928 935 features.add(override)
929 936
930 937 for imply in implies.get(override, []):
931 938 if imply.startswith('-'):
932 939 if imply[1:] in features:
933 940 features.remove(imply[1:])
934 941 else:
935 942 features.add(imply)
936 943
937 944 return features
938 945
939 946
940 947 @check('reporevlogstore', 'repository using the default revlog store')
941 948 def has_reporevlogstore():
942 949 return 'revlogstore' in getrepofeatures()
943 950
944 951
945 952 @check('reposimplestore', 'repository using simple storage extension')
946 953 def has_reposimplestore():
947 954 return 'simplestore' in getrepofeatures()
948 955
949 956
950 957 @check('repobundlerepo', 'whether we can open bundle files as repos')
951 958 def has_repobundlerepo():
952 959 return 'bundlerepo' in getrepofeatures()
953 960
954 961
955 962 @check('repofncache', 'repository has an fncache')
956 963 def has_repofncache():
957 964 return 'fncache' in getrepofeatures()
958 965
959 966
960 967 @check('sqlite', 'sqlite3 module is available')
961 968 def has_sqlite():
962 969 try:
963 970 import sqlite3
964 971
965 972 version = sqlite3.sqlite_version_info
966 973 except ImportError:
967 974 return False
968 975
969 976 if version < (3, 8, 3):
970 977 # WITH clause not supported
971 978 return False
972 979
973 980 return matchoutput('sqlite3 -version', br'^3\.\d+')
974 981
975 982
976 983 @check('vcr', 'vcr http mocking library')
977 984 def has_vcr():
978 985 try:
979 986 import vcr
980 987
981 988 vcr.VCR
982 989 return True
983 990 except (ImportError, AttributeError):
984 991 pass
985 992 return False
986 993
987 994
988 995 @check('emacs', 'GNU Emacs')
989 996 def has_emacs():
990 997 # Our emacs lisp uses `with-eval-after-load` which is new in emacs
991 998 # 24.4, so we allow emacs 24.4, 24.5, and 25+ (24.5 was the last
992 999 # 24 release)
993 1000 return matchoutput('emacs --version', b'GNU Emacs 2(4.4|4.5|5|6|7|8|9)')
994 1001
995 1002
996 1003 @check('black', 'the black formatter for python')
997 1004 def has_black():
998 1005 blackcmd = 'black --version'
999 1006 version_regex = b'black, version ([0-9a-b.]+)'
1000 1007 version = matchoutput(blackcmd, version_regex)
1001 1008 sv = distutils.version.StrictVersion
1002 1009 return version and sv(_strpath(version.group(1))) >= sv('19.10b0')
1003 1010
1004 1011
1005 1012 @check('pytype', 'the pytype type checker')
1006 1013 def has_pytype():
1007 1014 pytypecmd = 'pytype --version'
1008 1015 version = matchoutput(pytypecmd, b'[0-9a-b.]+')
1009 1016 sv = distutils.version.StrictVersion
1010 1017 return version and sv(_strpath(version.group(0))) >= sv('2019.10.17')
@@ -1,626 +1,626 b''
1 1 #require serve
2 2
3 3 $ hg init test
4 4 $ cd test
5 5 $ echo foo>foo
6 6 $ hg commit -Am 1 -d '1 0'
7 7 adding foo
8 8 $ echo bar>bar
9 9 $ hg commit -Am 2 -d '2 0'
10 10 adding bar
11 11 $ mkdir baz
12 12 $ echo bletch>baz/bletch
13 13 $ hg commit -Am 3 -d '1000000000 0'
14 14 adding baz/bletch
15 15 $ hg init subrepo
16 16 $ touch subrepo/sub
17 17 $ hg -q -R subrepo ci -Am "init subrepo"
18 18 $ echo "subrepo = subrepo" > .hgsub
19 19 $ hg add .hgsub
20 20 $ hg ci -m "add subrepo"
21 21
22 22 $ cat >> $HGRCPATH <<EOF
23 23 > [extensions]
24 24 > share =
25 25 > EOF
26 26
27 27 hg subrepos are shared when the parent repo is shared
28 28
29 29 $ cd ..
30 30 $ hg share test shared1
31 31 updating working directory
32 32 sharing subrepo subrepo from $TESTTMP/test/subrepo
33 33 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
34 34 $ cat shared1/subrepo/.hg/sharedpath
35 35 $TESTTMP/test/subrepo/.hg (no-eol)
36 36
37 37 hg subrepos are shared into existence on demand if the parent was shared
38 38
39 39 $ hg clone -qr 1 test clone1
40 40 $ hg share clone1 share2
41 41 updating working directory
42 42 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
43 43 $ hg -R clone1 -q pull
44 44 $ hg -R share2 update tip
45 45 sharing subrepo subrepo from $TESTTMP/test/subrepo
46 46 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
47 47 $ cat share2/subrepo/.hg/sharedpath
48 48 $TESTTMP/test/subrepo/.hg (no-eol)
49 49 $ echo 'mod' > share2/subrepo/sub
50 50 $ hg -R share2 ci -Sqm 'subrepo mod'
51 51 $ hg -R clone1 update -C tip
52 52 cloning subrepo subrepo from $TESTTMP/test/subrepo
53 53 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
54 54 $ find share2 | egrep 'sharedpath|00.+\.i' | sort
55 55 share2/.hg/sharedpath
56 56 share2/subrepo/.hg/sharedpath
57 57 $ hg -R share2 unshare
58 58 unsharing subrepo 'subrepo'
59 59 $ find share2 | egrep 'sharedpath|00.+\.i' | sort
60 60 share2/.hg/00changelog.i
61 61 share2/.hg/sharedpath.old
62 62 share2/.hg/store/00changelog.i
63 63 share2/.hg/store/00manifest.i
64 64 share2/subrepo/.hg/00changelog.i
65 65 share2/subrepo/.hg/sharedpath.old
66 66 share2/subrepo/.hg/store/00changelog.i
67 67 share2/subrepo/.hg/store/00manifest.i
68 68 $ hg -R share2/subrepo log -r tip -T compact
69 69 1[tip] 559dcc9bfa65 1970-01-01 00:00 +0000 test
70 70 subrepo mod
71 71
72 72 $ rm -rf clone1
73 73
74 74 $ hg clone -qr 1 test clone1
75 75 $ hg share clone1 shared3
76 76 updating working directory
77 77 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
78 78 $ hg -R clone1 -q pull
79 79 $ hg -R shared3 archive --config ui.archivemeta=False -r tip -S archive
80 80 sharing subrepo subrepo from $TESTTMP/test/subrepo
81 81 $ cat shared3/subrepo/.hg/sharedpath
82 82 $TESTTMP/test/subrepo/.hg (no-eol)
83 83 $ diff -r archive test
84 84 Only in test: .hg
85 85 Common subdirectories: archive/baz and test/baz (?)
86 86 Common subdirectories: archive/subrepo and test/subrepo (?)
87 87 Only in test/subrepo: .hg
88 88 [1]
89 89 $ rm -rf archive
90 90
91 91 $ cd test
92 92 $ echo "[web]" >> .hg/hgrc
93 93 $ echo "name = test-archive" >> .hg/hgrc
94 94 $ echo "archivesubrepos = True" >> .hg/hgrc
95 95 $ cp .hg/hgrc .hg/hgrc-base
96 96 > test_archtype() {
97 97 > echo "allow-archive = $1" >> .hg/hgrc
98 98 > test_archtype_run "$@"
99 99 > }
100 100 > test_archtype_deprecated() {
101 101 > echo "allow$1 = True" >> .hg/hgrc
102 102 > test_archtype_run "$@"
103 103 > }
104 104 > test_archtype_run() {
105 105 > hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log \
106 106 > --config extensions.blackbox= --config blackbox.track=develwarn
107 107 > cat hg.pid >> $DAEMON_PIDS
108 108 > echo % $1 allowed should give 200
109 109 > get-with-headers.py --bodyfile body localhost:$HGPORT "archive/tip.$2" -
110 110 > f --size --sha1 body
111 111 > echo % $3 and $4 disallowed should both give 403
112 112 > get-with-headers.py --bodyfile body localhost:$HGPORT "archive/tip.$3" -
113 113 > f --size --sha1 body
114 114 > get-with-headers.py --bodyfile body localhost:$HGPORT "archive/tip.$4" -
115 115 > f --size --sha1 body
116 116 > killdaemons.py
117 117 > cat errors.log
118 118 > hg blackbox --config extensions.blackbox= --config blackbox.track=
119 119 > cp .hg/hgrc-base .hg/hgrc
120 120 > }
121 121
122 122 check http return codes
123 123
124 124 $ test_archtype gz tar.gz tar.bz2 zip
125 125 % gz allowed should give 200
126 126 200 Script output follows
127 127 content-disposition: attachment; filename=test-archive-1701ef1f1510.tar.gz
128 128 content-type: application/x-gzip
129 129 date: $HTTP_DATE$
130 130 etag: W/"*" (glob)
131 131 server: testing stub value
132 132 transfer-encoding: chunked
133 133
134 134 body: size=408, sha1=8fa06531bddecc365a9f5edb0f88b65974bfe505 (no-py38 !)
135 135 body: size=506, sha1=70926a04cb8887d0bcccf5380488100a10222def (py38 !)
136 136 % tar.bz2 and zip disallowed should both give 403
137 137 403 Archive type not allowed: bz2
138 138 content-type: text/html; charset=ascii
139 139 date: $HTTP_DATE$
140 140 etag: W/"*" (glob)
141 141 server: testing stub value
142 142 transfer-encoding: chunked
143 143
144 144 body: size=1451, sha1=4c5cf0f574446c44feb7f88f4e0e2a56bd92c352
145 145 403 Archive type not allowed: zip
146 146 content-type: text/html; charset=ascii
147 147 date: $HTTP_DATE$
148 148 etag: W/"*" (glob)
149 149 server: testing stub value
150 150 transfer-encoding: chunked
151 151
152 152 body: size=1451, sha1=cbfa5574b337348bfd0564cc534474d002e7d6c7
153 153 $ test_archtype bz2 tar.bz2 zip tar.gz
154 154 % bz2 allowed should give 200
155 155 200 Script output follows
156 156 content-disposition: attachment; filename=test-archive-1701ef1f1510.tar.bz2
157 157 content-type: application/x-bzip2
158 158 date: $HTTP_DATE$
159 159 etag: W/"*" (glob)
160 160 server: testing stub value
161 161 transfer-encoding: chunked
162 162
163 163 body: size=426, sha1=8d87f5aba6e14f1bfea6c232985982c278b2fb0b (no-py38 !)
164 164 body: size=506, sha1=1bd1f8e8d3701704bd4385038bd9c09b81c77f4e (py38 !)
165 165 % zip and tar.gz disallowed should both give 403
166 166 403 Archive type not allowed: zip
167 167 content-type: text/html; charset=ascii
168 168 date: $HTTP_DATE$
169 169 etag: W/"*" (glob)
170 170 server: testing stub value
171 171 transfer-encoding: chunked
172 172
173 173 body: size=1451, sha1=cbfa5574b337348bfd0564cc534474d002e7d6c7
174 174 403 Archive type not allowed: gz
175 175 content-type: text/html; charset=ascii
176 176 date: $HTTP_DATE$
177 177 etag: W/"*" (glob)
178 178 server: testing stub value
179 179 transfer-encoding: chunked
180 180
181 181 body: size=1450, sha1=71f0b12d59f85fdcfe8ff493e2dc66863f2f7734
182 182 $ test_archtype zip zip tar.gz tar.bz2
183 183 % zip allowed should give 200
184 184 200 Script output follows
185 185 content-disposition: attachment; filename=test-archive-1701ef1f1510.zip
186 186 content-type: application/zip
187 187 date: $HTTP_DATE$
188 188 etag: W/"*" (glob)
189 189 server: testing stub value
190 190 transfer-encoding: chunked
191 191
192 192 body: size=(1377|1461|1489), sha1=(677b14d3d048778d5eb5552c14a67e6192068650|be6d3983aa13dfe930361b2569291cdedd02b537|1897e496871aa89ad685a92b936f5fa0d008b9e8) (re)
193 193 % tar.gz and tar.bz2 disallowed should both give 403
194 194 403 Archive type not allowed: gz
195 195 content-type: text/html; charset=ascii
196 196 date: $HTTP_DATE$
197 197 etag: W/"*" (glob)
198 198 server: testing stub value
199 199 transfer-encoding: chunked
200 200
201 201 body: size=1450, sha1=71f0b12d59f85fdcfe8ff493e2dc66863f2f7734
202 202 403 Archive type not allowed: bz2
203 203 content-type: text/html; charset=ascii
204 204 date: $HTTP_DATE$
205 205 etag: W/"*" (glob)
206 206 server: testing stub value
207 207 transfer-encoding: chunked
208 208
209 209 body: size=1451, sha1=4c5cf0f574446c44feb7f88f4e0e2a56bd92c352
210 210
211 211 check http return codes (with deprecated option)
212 212
213 213 $ test_archtype_deprecated gz tar.gz tar.bz2 zip
214 214 % gz allowed should give 200
215 215 200 Script output follows
216 216 content-disposition: attachment; filename=test-archive-1701ef1f1510.tar.gz
217 217 content-type: application/x-gzip
218 218 date: $HTTP_DATE$
219 219 etag: W/"*" (glob)
220 220 server: testing stub value
221 221 transfer-encoding: chunked
222 222
223 223 body: size=408, sha1=8fa06531bddecc365a9f5edb0f88b65974bfe505 (no-py38 !)
224 224 body: size=506, sha1=70926a04cb8887d0bcccf5380488100a10222def (py38 !)
225 225 % tar.bz2 and zip disallowed should both give 403
226 226 403 Archive type not allowed: bz2
227 227 content-type: text/html; charset=ascii
228 228 date: $HTTP_DATE$
229 229 etag: W/"*" (glob)
230 230 server: testing stub value
231 231 transfer-encoding: chunked
232 232
233 233 body: size=1451, sha1=4c5cf0f574446c44feb7f88f4e0e2a56bd92c352
234 234 403 Archive type not allowed: zip
235 235 content-type: text/html; charset=ascii
236 236 date: $HTTP_DATE$
237 237 etag: W/"*" (glob)
238 238 server: testing stub value
239 239 transfer-encoding: chunked
240 240
241 241 body: size=1451, sha1=cbfa5574b337348bfd0564cc534474d002e7d6c7
242 242 $ test_archtype_deprecated bz2 tar.bz2 zip tar.gz
243 243 % bz2 allowed should give 200
244 244 200 Script output follows
245 245 content-disposition: attachment; filename=test-archive-1701ef1f1510.tar.bz2
246 246 content-type: application/x-bzip2
247 247 date: $HTTP_DATE$
248 248 etag: W/"*" (glob)
249 249 server: testing stub value
250 250 transfer-encoding: chunked
251 251
252 252 body: size=426, sha1=8d87f5aba6e14f1bfea6c232985982c278b2fb0b (no-py38 !)
253 253 body: size=506, sha1=1bd1f8e8d3701704bd4385038bd9c09b81c77f4e (py38 !)
254 254 % zip and tar.gz disallowed should both give 403
255 255 403 Archive type not allowed: zip
256 256 content-type: text/html; charset=ascii
257 257 date: $HTTP_DATE$
258 258 etag: W/"*" (glob)
259 259 server: testing stub value
260 260 transfer-encoding: chunked
261 261
262 262 body: size=1451, sha1=cbfa5574b337348bfd0564cc534474d002e7d6c7
263 263 403 Archive type not allowed: gz
264 264 content-type: text/html; charset=ascii
265 265 date: $HTTP_DATE$
266 266 etag: W/"*" (glob)
267 267 server: testing stub value
268 268 transfer-encoding: chunked
269 269
270 270 body: size=1450, sha1=71f0b12d59f85fdcfe8ff493e2dc66863f2f7734
271 271 $ test_archtype_deprecated zip zip tar.gz tar.bz2
272 272 % zip allowed should give 200
273 273 200 Script output follows
274 274 content-disposition: attachment; filename=test-archive-1701ef1f1510.zip
275 275 content-type: application/zip
276 276 date: $HTTP_DATE$
277 277 etag: W/"*" (glob)
278 278 server: testing stub value
279 279 transfer-encoding: chunked
280 280
281 281 body: size=(1377|1461|1489), sha1=(677b14d3d048778d5eb5552c14a67e6192068650|be6d3983aa13dfe930361b2569291cdedd02b537|1897e496871aa89ad685a92b936f5fa0d008b9e8) (re)
282 282 % tar.gz and tar.bz2 disallowed should both give 403
283 283 403 Archive type not allowed: gz
284 284 content-type: text/html; charset=ascii
285 285 date: $HTTP_DATE$
286 286 etag: W/"*" (glob)
287 287 server: testing stub value
288 288 transfer-encoding: chunked
289 289
290 290 body: size=1450, sha1=71f0b12d59f85fdcfe8ff493e2dc66863f2f7734
291 291 403 Archive type not allowed: bz2
292 292 content-type: text/html; charset=ascii
293 293 date: $HTTP_DATE$
294 294 etag: W/"*" (glob)
295 295 server: testing stub value
296 296 transfer-encoding: chunked
297 297
298 298 body: size=1451, sha1=4c5cf0f574446c44feb7f88f4e0e2a56bd92c352
299 299
300 300 $ echo "allow-archive = gz bz2 zip" >> .hg/hgrc
301 301 $ hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log
302 302 $ cat hg.pid >> $DAEMON_PIDS
303 303
304 304 check archive links' order
305 305
306 306 $ get-with-headers.py localhost:$HGPORT "?revcount=1" | grep '/archive/tip.'
307 307 <a href="/archive/tip.zip">zip</a>
308 308 <a href="/archive/tip.tar.gz">gz</a>
309 309 <a href="/archive/tip.tar.bz2">bz2</a>
310 310
311 311 invalid arch type should give 404
312 312
313 313 $ get-with-headers.py localhost:$HGPORT "archive/tip.invalid" | head -n 1
314 314 404 Unsupported archive type: None
315 315
316 316 $ TIP=`hg id -v | cut -f1 -d' '`
317 317 $ QTIP=`hg id -q`
318 318 $ cat > getarchive.py <<EOF
319 319 > from __future__ import absolute_import
320 320 > import os
321 321 > import sys
322 322 > from mercurial import (
323 323 > util,
324 324 > )
325 325 > try:
326 326 > # Set stdout to binary mode for win32 platforms
327 327 > import msvcrt
328 328 > msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
329 329 > except ImportError:
330 330 > pass
331 331 > if len(sys.argv) <= 3:
332 332 > node, archive = sys.argv[1:]
333 333 > requeststr = 'cmd=archive;node=%s;type=%s' % (node, archive)
334 334 > else:
335 335 > node, archive, file = sys.argv[1:]
336 336 > requeststr = 'cmd=archive;node=%s;type=%s;file=%s' % (node, archive, file)
337 337 > try:
338 338 > stdout = sys.stdout.buffer
339 339 > except AttributeError:
340 340 > stdout = sys.stdout
341 341 > try:
342 342 > f = util.urlreq.urlopen('http://$LOCALIP:%s/?%s'
343 343 > % (os.environ['HGPORT'], requeststr))
344 344 > stdout.write(f.read())
345 345 > except util.urlerr.httperror as e:
346 346 > sys.stderr.write(str(e) + '\n')
347 347 > EOF
348 348 $ "$PYTHON" getarchive.py "$TIP" gz | gunzip | tar tf - 2>/dev/null
349 349 test-archive-1701ef1f1510/.hg_archival.txt
350 350 test-archive-1701ef1f1510/.hgsub
351 351 test-archive-1701ef1f1510/.hgsubstate
352 352 test-archive-1701ef1f1510/bar
353 353 test-archive-1701ef1f1510/baz/bletch
354 354 test-archive-1701ef1f1510/foo
355 355 test-archive-1701ef1f1510/subrepo/sub
356 356 $ "$PYTHON" getarchive.py "$TIP" bz2 | bunzip2 | tar tf - 2>/dev/null
357 357 test-archive-1701ef1f1510/.hg_archival.txt
358 358 test-archive-1701ef1f1510/.hgsub
359 359 test-archive-1701ef1f1510/.hgsubstate
360 360 test-archive-1701ef1f1510/bar
361 361 test-archive-1701ef1f1510/baz/bletch
362 362 test-archive-1701ef1f1510/foo
363 363 test-archive-1701ef1f1510/subrepo/sub
364 364 $ "$PYTHON" getarchive.py "$TIP" zip > archive.zip
365 365 $ unzip -t archive.zip
366 366 Archive: archive.zip
367 367 testing: test-archive-1701ef1f1510/.hg_archival.txt*OK (glob)
368 368 testing: test-archive-1701ef1f1510/.hgsub*OK (glob)
369 369 testing: test-archive-1701ef1f1510/.hgsubstate*OK (glob)
370 370 testing: test-archive-1701ef1f1510/bar*OK (glob)
371 371 testing: test-archive-1701ef1f1510/baz/bletch*OK (glob)
372 372 testing: test-archive-1701ef1f1510/foo*OK (glob)
373 373 testing: test-archive-1701ef1f1510/subrepo/sub*OK (glob)
374 374 No errors detected in compressed data of archive.zip.
375 375
376 376 test that we can download single directories and files
377 377
378 378 $ "$PYTHON" getarchive.py "$TIP" gz baz | gunzip | tar tf - 2>/dev/null
379 379 test-archive-1701ef1f1510/baz/bletch
380 380 $ "$PYTHON" getarchive.py "$TIP" gz foo | gunzip | tar tf - 2>/dev/null
381 381 test-archive-1701ef1f1510/foo
382 382
383 383 test that we detect file patterns that match no files
384 384
385 385 $ "$PYTHON" getarchive.py "$TIP" gz foobar
386 386 HTTP Error 404: file(s) not found: foobar
387 387
388 388 test that we reject unsafe patterns
389 389
390 390 $ "$PYTHON" getarchive.py "$TIP" gz relre:baz
391 391 HTTP Error 404: file(s) not found: relre:baz
392 392
393 393 $ killdaemons.py
394 394
395 395 $ hg archive -t tar test.tar
396 396 $ tar tf test.tar
397 397 test/.hg_archival.txt
398 398 test/.hgsub
399 399 test/.hgsubstate
400 400 test/bar
401 401 test/baz/bletch
402 402 test/foo
403 403
404 404 $ hg archive --debug -t tbz2 -X baz test.tar.bz2 --config progress.debug=true
405 405 archiving: 0/4 files (0.00%)
406 406 archiving: .hgsub 1/4 files (25.00%)
407 407 archiving: .hgsubstate 2/4 files (50.00%)
408 408 archiving: bar 3/4 files (75.00%)
409 409 archiving: foo 4/4 files (100.00%)
410 410 $ bunzip2 -dc test.tar.bz2 | tar tf - 2>/dev/null
411 411 test/.hg_archival.txt
412 412 test/.hgsub
413 413 test/.hgsubstate
414 414 test/bar
415 415 test/foo
416 416
417 417 $ hg archive -t tgz -p %b-%h test-%h.tar.gz
418 418 $ gzip -dc test-$QTIP.tar.gz | tar tf - 2>/dev/null
419 419 test-1701ef1f1510/.hg_archival.txt
420 420 test-1701ef1f1510/.hgsub
421 421 test-1701ef1f1510/.hgsubstate
422 422 test-1701ef1f1510/bar
423 423 test-1701ef1f1510/baz/bletch
424 424 test-1701ef1f1510/foo
425 425
426 426 $ hg archive autodetected_test.tar
427 427 $ tar tf autodetected_test.tar
428 428 autodetected_test/.hg_archival.txt
429 429 autodetected_test/.hgsub
430 430 autodetected_test/.hgsubstate
431 431 autodetected_test/bar
432 432 autodetected_test/baz/bletch
433 433 autodetected_test/foo
434 434
435 435 The '-t' should override autodetection
436 436
437 437 $ hg archive -t tar autodetect_override_test.zip
438 438 $ tar tf autodetect_override_test.zip
439 439 autodetect_override_test.zip/.hg_archival.txt
440 440 autodetect_override_test.zip/.hgsub
441 441 autodetect_override_test.zip/.hgsubstate
442 442 autodetect_override_test.zip/bar
443 443 autodetect_override_test.zip/baz/bletch
444 444 autodetect_override_test.zip/foo
445 445
446 446 $ for ext in tar tar.gz tgz tar.bz2 tbz2 zip; do
447 447 > hg archive auto_test.$ext
448 448 > if [ -d auto_test.$ext ]; then
449 449 > echo "extension $ext was not autodetected."
450 450 > fi
451 451 > done
452 452
453 453 $ cat > md5comp.py <<EOF
454 454 > from __future__ import absolute_import, print_function
455 455 > import hashlib
456 456 > import sys
457 457 > f1, f2 = sys.argv[1:3]
458 458 > h1 = hashlib.md5(open(f1, 'rb').read()).hexdigest()
459 459 > h2 = hashlib.md5(open(f2, 'rb').read()).hexdigest()
460 460 > print(h1 == h2 or "md5 differ: " + repr((h1, h2)))
461 461 > EOF
462 462
463 463 archive name is stored in the archive, so create similar archives and
464 464 rename them afterwards.
465 465
466 466 $ hg archive -t tgz tip.tar.gz
467 467 $ mv tip.tar.gz tip1.tar.gz
468 468 $ sleep 1
469 469 $ hg archive -t tgz tip.tar.gz
470 470 $ mv tip.tar.gz tip2.tar.gz
471 471 $ "$PYTHON" md5comp.py tip1.tar.gz tip2.tar.gz
472 472 True
473 473
474 474 $ hg archive -t zip -p /illegal test.zip
475 475 abort: archive prefix contains illegal components
476 476 [255]
477 477 $ hg archive -t zip -p very/../bad test.zip
478 478
479 479 $ hg archive --config ui.archivemeta=false -t zip -r 2 test.zip
480 480 $ unzip -t test.zip
481 481 Archive: test.zip
482 482 testing: test/bar*OK (glob)
483 483 testing: test/baz/bletch*OK (glob)
484 484 testing: test/foo*OK (glob)
485 485 No errors detected in compressed data of test.zip.
486 486
487 487 $ hg archive -t tar - | tar tf - 2>/dev/null
488 488 test-1701ef1f1510/.hg_archival.txt
489 489 test-1701ef1f1510/.hgsub
490 490 test-1701ef1f1510/.hgsubstate
491 491 test-1701ef1f1510/bar
492 492 test-1701ef1f1510/baz/bletch
493 493 test-1701ef1f1510/foo
494 494
495 495 $ hg archive -r 0 -t tar rev-%r.tar
496 496 $ [ -f rev-0.tar ]
497 497
498 498 test .hg_archival.txt
499 499
500 500 $ hg archive ../test-tags
501 501 $ cat ../test-tags/.hg_archival.txt
502 502 repo: daa7f7c60e0a224faa4ff77ca41b2760562af264
503 503 node: 1701ef1f151069b8747038e93b5186bb43a47504
504 504 branch: default
505 505 latesttag: null
506 506 latesttagdistance: 4
507 507 changessincelatesttag: 4
508 508 $ hg tag -r 2 mytag
509 509 $ hg tag -r 2 anothertag
510 510 $ hg archive -r 2 ../test-lasttag
511 511 $ cat ../test-lasttag/.hg_archival.txt
512 512 repo: daa7f7c60e0a224faa4ff77ca41b2760562af264
513 513 node: 2c0277f05ed49d1c8328fb9ba92fba7a5ebcb33e
514 514 branch: default
515 515 tag: anothertag
516 516 tag: mytag
517 517
518 518 $ hg archive -t bogus test.bogus
519 519 abort: unknown archive type 'bogus'
520 520 [255]
521 521
522 522 enable progress extension:
523 523
524 524 $ cp $HGRCPATH $HGRCPATH.no-progress
525 525 $ cat >> $HGRCPATH <<EOF
526 526 > [progress]
527 527 > assume-tty = 1
528 528 > format = topic bar number
529 529 > delay = 0
530 530 > refresh = 0
531 531 > width = 60
532 532 > EOF
533 533
534 534 $ hg archive ../with-progress
535 535 \r (no-eol) (esc)
536 536 archiving [ ] 0/6\r (no-eol) (esc)
537 537 archiving [======> ] 1/6\r (no-eol) (esc)
538 538 archiving [=============> ] 2/6\r (no-eol) (esc)
539 539 archiving [====================> ] 3/6\r (no-eol) (esc)
540 540 archiving [===========================> ] 4/6\r (no-eol) (esc)
541 541 archiving [==================================> ] 5/6\r (no-eol) (esc)
542 542 archiving [==========================================>] 6/6\r (no-eol) (esc)
543 543 \r (no-eol) (esc)
544 544
545 545 cleanup after progress extension test:
546 546
547 547 $ cp $HGRCPATH.no-progress $HGRCPATH
548 548
549 549 server errors
550 550
551 551 $ cat errors.log
552 552
553 553 empty repo
554 554
555 555 $ hg init ../empty
556 556 $ cd ../empty
557 557 $ hg archive ../test-empty
558 558 abort: no working directory: please specify a revision
559 559 [255]
560 560
561 561 old file -- date clamped to 1980
562 562
563 563 $ touch -t 197501010000 old
564 564 $ hg add old
565 565 $ hg commit -m old
566 566 $ hg archive ../old.zip
567 567 $ unzip -l ../old.zip | grep -v -- ----- | egrep -v files$
568 568 Archive: ../old.zip
569 569 \s*Length.* (re)
570 570 *172*80*00:00*old/.hg_archival.txt (glob)
571 571 *0*80*00:00*old/old (glob)
572 572
573 573 test xz support only available in Python 3.4
574 574
575 575 #if py3
576 576 $ hg archive ../archive.txz
577 $ xz -l ../archive.txz | head -n1
578 Strms Blocks Compressed Uncompressed Ratio Check Filename
577 $ which xz >/dev/null && xz -l ../archive.txz | head -n1 || true
578 Strms Blocks Compressed Uncompressed Ratio Check Filename (xz !)
579 579 $ rm -f ../archive.txz
580 580 #else
581 581 $ hg archive ../archive.txz
582 582 abort: xz compression is only available in Python 3
583 583 [255]
584 584 #endif
585 585
586 586 show an error when a provided pattern matches no files
587 587
588 588 $ hg archive -I file_that_does_not_exist.foo ../empty.zip
589 589 abort: no files match the archive pattern
590 590 [255]
591 591
592 592 $ hg archive -X * ../empty.zip
593 593 abort: no files match the archive pattern
594 594 [255]
595 595
596 596 $ cd ..
597 597
598 598 issue3600: check whether "hg archive" can create archive files which
599 599 are extracted with expected timestamp, even though TZ is not
600 600 configured as GMT.
601 601
602 602 $ mkdir issue3600
603 603 $ cd issue3600
604 604
605 605 $ hg init repo
606 606 $ echo a > repo/a
607 607 $ hg -R repo add repo/a
608 608 $ hg -R repo commit -m '#0' -d '456789012 21600'
609 609 $ cat > show_mtime.py <<EOF
610 610 > from __future__ import absolute_import, print_function
611 611 > import os
612 612 > import sys
613 613 > print(int(os.stat(sys.argv[1]).st_mtime))
614 614 > EOF
615 615
616 616 $ hg -R repo archive --prefix tar-extracted archive.tar
617 617 $ (TZ=UTC-3; export TZ; tar xf archive.tar)
618 618 $ "$PYTHON" show_mtime.py tar-extracted/a
619 619 456789012
620 620
621 621 $ hg -R repo archive --prefix zip-extracted archive.zip
622 622 $ (TZ=UTC-3; export TZ; unzip -q archive.zip)
623 623 $ "$PYTHON" show_mtime.py zip-extracted/a
624 624 456789012
625 625
626 626 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now