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