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